﻿using MediaScoutGUI.GUITypes;
using System.IO;
using System.Collections.ObjectModel;
using System.Xml;
using TouchRC.MetadataCaching.Extensions;
using System.Xml.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using TouchRC.Logging;

namespace TouchRC.MetadataCaching
{
    public class CachingHelper
    {
        #region Private Members

        public delegate void ProgressReporter(string status);
        ProgressReporter m_ProgressReporter = null;
        #endregion

        #region Constructors

        public CachingHelper()
        {
        }

        public CachingHelper(ProgressReporter progressReporter)
        {
            m_ProgressReporter = progressReporter;
        }

        #endregion

        private enum CacheType
        {
            Xml,
            Poster,
            Backdrop
        }

        public void CacheMedia(DirectoryInfo cacheDir)
        {
            List<VirtualDirectory> virtualDirs = new List<VirtualDirectory>();

            FileInfo[] vfFiles = cacheDir.GetFiles("*.vf");
            string message = String.Format("Found {0} virtual directories", vfFiles.Count()); ;
            PrintMessage(message);

            if (vfFiles.Length > 0)
            {
                foreach (FileInfo vf in vfFiles)
                {
                    try
                    {
                        // Create an instance of StreamReader to read from a file.
                        // The using statement also closes the StreamReader.
                        using (StreamReader sr = new StreamReader(vf.FullName))
                        {
                            VirtualDirectory vDir = new VirtualDirectory();
                            String line;
                            while ((line = sr.ReadLine()) != null)
                            {
                                if (line.StartsWith("folder:"))
                                {
                                    int index = line.IndexOf(':');
                                    string value = line.Substring(index + 1);

                                    DirectoryInfo dir = new DirectoryInfo(value);
                                    if (Directory.Exists(dir.FullName))
                                    {
                                        vDir.Add(dir);
                                    }
                                }
                            }

                            if (vDir.Count > 0)
                            {
                                vDir.Name = vf.GetFileNameWithoutExtension();
                                virtualDirs.Add(vDir);
                                message += String.Format("\r\nAdding {0}", vDir.Name);
                                PrintMessage(message);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Logger.LogMessage("Error parsing .vf file: {0}", e.Message);
                        // add logging at some point.
                    }

                }
            }
            foreach (VirtualDirectory vDir in virtualDirs)
            {
                m_CacheMediaString = string.Empty;
                CacheMedia(vDir, cacheDir);
            }
        }

        private string m_CacheMediaString = string.Empty;

        private void CacheMedia(VirtualDirectory virtualDirectory, DirectoryInfo cacheDir)
        {
            List<Movie> movies = new List<Movie>();
            List<TVShow> tvshows = new List<TVShow>();

            m_CacheMediaString = string.Format("Caching {0}\r\n", virtualDirectory.Name);
            PrintMessage(m_CacheMediaString);

            DirectoryInfo virtualDirCacheDir = new DirectoryInfo(Path.Combine(cacheDir.FullName, virtualDirectory.Name));
            virtualDirCacheDir.CheckDirectoryCreation();

            foreach (DirectoryInfo dirs in virtualDirectory)
            {
                DirectoryInfo[] dirsDirs = dirs.GetDirectories();
                foreach (DirectoryInfo di in dirsDirs)
                {
                    try
                    {
                        Movie m = new Movie(di);
                        if (!string.IsNullOrEmpty(m.Name))
                        {
                            movies.Add(m);
                            CacheMovie(virtualDirCacheDir, m);
                        }
                        else
                        {
                            TVShow t = new TVShow(di);
                            if (!string.IsNullOrEmpty(t.Name))
                            {
                                tvshows.Add(t);
                                CacheTv(virtualDirCacheDir, t);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        // add error stuff here.
                    }
                }
            }
            m_CacheMediaString += "\r\nWriting Summary...";
            PrintMessage(m_CacheMediaString);
            WriteSummary(virtualDirCacheDir, movies, tvshows);
            m_CacheMediaString += "done.";
            PrintMessage(m_CacheMediaString);
        }

        private void CacheTv(DirectoryInfo cacheDir, TVShow tvShow)
        {
            DirectoryInfo TVCacheDirectory = new DirectoryInfo(Path.Combine(cacheDir.FullName, "TV"));
            TVCacheDirectory.CheckDirectoryCreation();

            string progressMessage = String.Format("{0}\r\n{1}\r\n\r\nCaching Metadata..", m_CacheMediaString, tvShow.Name);
            PrintMessage(progressMessage);
            
            //Cache metadata
            if (tvShow.HasMetadata)
            {
                DirectoryInfo showCacheDir = new DirectoryInfo(Path.Combine(TVCacheDirectory.FullName, tvShow.Id.ToString()));
                showCacheDir.CheckDirectoryCreation();

                CacheMetadata(tvShow.Filepath.GetTvSeriesXml(), CachedFileName(showCacheDir.FullName, tvShow.Id.ToString(), CacheType.Xml));
                if (tvShow.HasPoster)
                {
                    progressMessage += "\r\nCaching Poster..";
                    PrintMessage(progressMessage);
                    CacheImage(tvShow.Filepath.GetFolderPoster(), CachedFileName(TVCacheDirectory.FullName, tvShow.Id.ToString(), CacheType.Poster));
                    progressMessage += "done.";
                    PrintMessage(progressMessage);
                }
                if (tvShow.HasBackdrop)
                {
                    progressMessage += "\r\nCaching Backdrop..";
                    PrintMessage(progressMessage);
                    CacheImage(tvShow.Filepath.GetFolderBackdrop(), CachedFileName(TVCacheDirectory.FullName, tvShow.Id.ToString(), CacheType.Backdrop));
                    progressMessage += "done.";
                    PrintMessage(progressMessage);
                }

                if (tvShow.Seasons.Count > 0)
                {
                    foreach (Season season in tvShow.Seasons)
                    {
                        string secondProgressMessage = progressMessage + String.Format("\r\nCaching Season: {0}", season.Name);
                        PrintMessage(secondProgressMessage);
                        DirectoryInfo seasonDir = new DirectoryInfo(Path.Combine(showCacheDir.FullName, season.Name));
                        seasonDir.CheckDirectoryCreation();

                        foreach (Episode episode in season.Episodes)
                        {
                            PrintMessage(secondProgressMessage + String.Format("\r\nCaching Episode {0}", episode.EpisodeNumber));
                            if (episode.HasMetadata)
                            {
                                CacheMetadata(episode.MetadataPath, CachedFileName(seasonDir.FullName, episode.Id, CacheType.Xml));
                            }

                            //if (episode.Poster != null && File.Exists(episode.Poster.FullName))
                            //{
                            //    CacheImage(episode.Poster,
                            //        CachedFileName(TVCacheDirectory.FullName, episode.Poster.GetFileNameWithoutExtension(), CacheType.Poster));
                            //}
                        }
                    }
                }
            }
        }

        private void CacheMetadata(FileInfo metaData, FileInfo cachedMetadata)
        {
            try
            {
                DateTime original = File.GetLastWriteTime(metaData.FullName);

                if (File.Exists(cachedMetadata.FullName) &&
                    (DateTime.Compare(File.GetLastWriteTime(cachedMetadata.FullName), original) >= 0))
                {
                    // do nothing, it's up to date...
                }
                else
                {
                    Message("Caching Metadata", DateTime.Now);
                    XmlDocument xdoc = new XmlDocument();
                    xdoc.Load(metaData.FullName);
                    xdoc.Save(cachedMetadata.FullName);
                }
            }
            catch (Exception ex)
            {
                Message("Error caching metadata: " + ex.Message, DateTime.Now);
            }
        }

        private void CacheMovie(DirectoryInfo cacheDir, Movie movie)
        {
            DirectoryInfo MovieCacheDirectory = new DirectoryInfo(Path.Combine(cacheDir.FullName, "Movies"));
            MovieCacheDirectory.CheckDirectoryCreation();

            string progressMessage = String.Format("{0}\r\n{1}\r\n\r\nCaching Metadata..", m_CacheMediaString, movie.Name);
            PrintMessage(progressMessage);
            //Cache metadata
            CacheMetadata(movie.Filepath.GetMyMoviesXml(), CachedFileName(MovieCacheDirectory.FullName, movie.Id.ToString(), CacheType.Xml));
            progressMessage += "done.";
            PrintMessage(progressMessage);

            if (movie.HasPoster)
            {
                progressMessage += "\r\nCaching Poster..";
                PrintMessage(progressMessage);
                CacheImage(movie.Filepath.GetFolderPoster(), CachedFileName(MovieCacheDirectory.FullName, movie.Id.ToString(), CacheType.Poster));
                progressMessage += "done.";
                PrintMessage(progressMessage);
            }
            if (movie.HasBackdrop)
            {
                progressMessage += "\r\nCaching Backdrop..";
                PrintMessage(progressMessage);
                CacheImage(movie.Filepath.GetFolderBackdrop(), CachedFileName(MovieCacheDirectory.FullName, movie.Id.ToString(), CacheType.Backdrop));
                progressMessage += "done.";
                PrintMessage(progressMessage);
            }
        }

        private void CacheImage(FileInfo image, FileInfo cachedImage)
        {
            bool resizeImage = false;

            if (!Directory.Exists(cachedImage.Directory.FullName))
                Directory.CreateDirectory(cachedImage.Directory.FullName);

            if (image != null && File.Exists(image.FullName))
            {
                DateTime original = File.GetLastWriteTime(image.FullName);

                if (File.Exists(cachedImage.FullName) &&
                    (DateTime.Compare(File.GetLastWriteTime(cachedImage.FullName), original) >= 0))
                {
                    // do nothing, it's up to date...
                }
                else
                {
                    if (resizeImage)
                    {
                        throw new NotImplementedException("Can't resize images just yet. Soon tho...");
                    }
                    else
                    {
                        File.Copy(image.FullName, cachedImage.FullName, true);
                    }
                }
            }
        }

        private FileInfo CachedFileName(string cacheDir, string id, CacheType type)
        {
            switch (type)
            {
                case CacheType.Xml:
                    {
                        if (!string.IsNullOrEmpty(id))
                            return new FileInfo(Path.Combine(cacheDir, id.ToString() + ".xml"));
                        else
                            return null;
                    }
                case CacheType.Poster:
                    {
                        string imagesDir = Path.Combine(cacheDir, "Images");
                        return new FileInfo(Path.Combine(imagesDir, id.ToString() + ".jpg"));
                    }
                case CacheType.Backdrop:
                    {
                        string imagesDir = Path.Combine(cacheDir, "Images");
                        return new FileInfo(Path.Combine(imagesDir, id.ToString() + "_Backdrop.jpg"));
                    }
                default:
                    return null;
            }
        }

        private void Message(string message, DateTime time)
        {
        }

        private void WriteSummary(DirectoryInfo virtualDirCache, List<Movie> movies, List<TVShow> tvshows)
        {
            FileInfo summaryFile = new FileInfo(Path.Combine(virtualDirCache.FullName, "Summary.xml"));

            if (File.Exists(summaryFile.FullName))
                File.Delete(summaryFile.FullName);

            XElement xml = new XElement("Summary",

                new XElement("Movies",
                from movie in movies
                select new XElement("Movie",
                    new XElement("Name", movie.Name),
                    new XElement("AssociatedMediaFiles",
                        from file in movie.AssociatedMediaFiles
                        select new XElement("File", file.FullName)),
                    new XElement("Id", movie.Id),
                    new XElement("Metadata", CachedFileName(@"Movies\", movie.Id.ToString(), CacheType.Xml)),
                    new XElement("Poster", movie.HasPoster ? CachedFileName(@"Movies\", movie.Id.ToString(), CacheType.Poster) : null),
                    new XElement("Backdrop", movie.HasBackdrop ? CachedFileName(@"Movies\", movie.Id.ToString(), CacheType.Backdrop) : null),
                    new XElement("Description", movie.Description)
                        )
                ),

                new XElement("TvShows",
                    from tv in tvshows
                    select new XElement("TvShow",
                        new XElement("Name", tv.Name),
                        new XElement("Id", tv.Id),
                        new XElement("TvShowMetadata", CachedFileName(@"TV\" + tv.Id, tv.Id.ToString(), CacheType.Xml)),
                        new XElement("TvShowPoster", tv.HasPoster ? CachedFileName(@"TV\", tv.Id.ToString(), CacheType.Poster) : null),
                        new XElement("TvShowBackdrop", tv.HasBackdrop ? CachedFileName(@"TV\", tv.Id.ToString(), CacheType.Backdrop) : null),
                        new XElement("TvShowOverview", tv.Overview),
                        new XElement("Seasons",
                        from season in tv.Seasons
                        select new XElement("Season",
                            new XElement("Name", season.Name),
                            new XElement("Episodes",
                            from episode in season.Episodes
                            select new XElement("Episode",
                                new XElement("Name", episode.Epname),
                                new XElement("AssociatedFile", episode.Filepath.FullName),
                                new XElement("EpisodeId", episode.Id),
                                new XElement("EpisodeNumber", episode.EpisodeNumber),
                                new XElement("EpisodeMetadata", CachedFileName(@"TV\" + tv.Id.ToString() + "\\" + season.Name, episode.Id, CacheType.Xml)),
                                new XElement("EpisodeSummary", episode.Description)
                                    )
                                    )
                                    )
                                )
                            )
                 )
                    );
            xml.Save(summaryFile.FullName);
        }

        public MetadataCollection ReadSummaries(DirectoryInfo cacheDir)
        {
            MetadataCollection caches = new MetadataCollection();
            if (Directory.Exists(cacheDir.FullName))
            {
                foreach (DirectoryInfo dir in cacheDir.GetVirtualCacheDirectories())
                {
                    VirtualCache cache = ReadSummary(dir);
                    if (cache != null)
                    {
                        caches.Add(cache);
                    }
                }
            }
            return caches;
        }

        private VirtualCache ReadSummary(DirectoryInfo virtualCacheDir)
        {
            VirtualCache retCache = null;

            FileInfo summaryFile = new FileInfo(Path.Combine(virtualCacheDir.FullName, "Summary.xml"));

            if (!File.Exists(summaryFile.FullName))
                return null;

            TvShowCollection tvShows = new TvShowCollection();
            MovieCollection movies = new MovieCollection();

            using (XmlReader reader = XmlReader.Create(summaryFile.FullName))
            {
                XDocument summaryDoc = XDocument.Load(reader);
                var moviesVar = from movie in summaryDoc.Descendants("Movies").Elements("Movie")
                                select new MovieSummary
                                {
                                    Name = movie.Element("Name").Value,
                                    Id = movie.Element("Id").Value,
                                    Description = movie.Element("Description").Value,
                                    MetadataFile = GetCacheFileInfoFromXElement(summaryFile.Directory, movie.Element("Metadata")),
                                    Poster = GetCacheFileInfoFromXElement(summaryFile.Directory, movie.Element("Poster")),
                                    Backdrop = GetCacheFileInfoFromXElement(summaryFile.Directory, movie.Element("Backdrop")),
                                    AssociatedFiles = GetAssocFilesFromVar(movie)
                                };
                foreach (MovieSummary movie in moviesVar)
                {
                    movies.Add(movie);
                }

                var seriesVar = from tvShow in summaryDoc.Descendants("TvShow")
                                select new TvShowSummary
                                {
                                    Name = tvShow.Element("Name").Value,
                                    Id = GetIntFromElement(tvShow.Element("Id")),
                                    Description = tvShow.Element("TvShowOverview").Value,
                                    Metadata = GetCacheFileInfoFromXElement(summaryFile.Directory, tvShow.Element("TvShowMetadata")),
                                    Poster = GetCacheFileInfoFromXElement(summaryFile.Directory, tvShow.Element("TvShowPoster")),
                                    Backdrop = GetCacheFileInfoFromXElement(summaryFile.Directory, tvShow.Element("TvShowBackdrop")),
                                    Seasons = GetSeasonsFromElement(summaryFile.Directory, tvShow.Element("Seasons"),
                                                GetCacheFileInfoFromXElement(summaryFile.Directory, tvShow.Element("TvShowPoster")),
                                                GetCacheFileInfoFromXElement(summaryFile.Directory, tvShow.Element("TvShowBackdrop")))
                                };

                foreach (TvShowSummary tvShow in seriesVar)
                {
                    tvShows.Add(tvShow);
                }

                VirtualCache cache = new VirtualCache();
                cache.Name = virtualCacheDir.Name;
                cache.Movies = movies;
                cache.TvShows = tvShows;

                retCache = cache;
            }

            return retCache;
        }

        #region Summary Xml Parsers

        private List<FileInfo> GetAssocFilesFromVar(XElement movies)
        {
            List<FileInfo> retVal = new List<FileInfo>();

            var filesVar = from fileAssoc in movies.Descendants("AssociatedMediaFiles").Elements("File")
                           select GetFileInfoFromElement(fileAssoc);

            foreach (FileInfo file in filesVar)
            {
                retVal.Add(file);
            }
            return retVal;
        }

        private SeasonCollection GetSeasonsFromElement(DirectoryInfo cacheDir, XElement xElement, FileInfo poster, FileInfo backdrop)
        {
            SeasonCollection seasons = new SeasonCollection();

            var seasonVar = from season in xElement.Elements()
                            select new SeasonSummary
                            {
                                Name = season.Element("Name").Value,
                                Episodes = GetEpisodesFromElement(cacheDir, season.Element("Episodes"), poster, backdrop)
                            };

            foreach (SeasonSummary season in seasonVar)
            {
                seasons.Add(season);
            }

            return seasons;
        }

        private EpisodeCollection GetEpisodesFromElement(DirectoryInfo cacheDir, XElement xElement, FileInfo poster, FileInfo backdrop)
        {
            EpisodeCollection episodes = new EpisodeCollection();

            var episodeVar = from episode in xElement.Descendants("Episode")
                             select new EpisodeSummary
                             {
                                 Name = episode.Element("Name").Value,
                                 AssociatedFile = GetFileInfoFromElement(episode.Element("AssociatedFile")),
                                 Description = episode.Element("EpisodeSummary").Value,
                                 EpisodeNumber = GetIntFromElement(episode.Element("EpisodeNumber")),
                                 Id = GetIntFromElement(episode.Element("EpisodeId")),
                                 MetadataFile = GetCacheFileInfoFromXElement(cacheDir, episode.Element("EpisodeMetadata")),
                                 Backdrop = backdrop,
                                 Poster = poster
                             };

            foreach (EpisodeSummary episode in episodeVar)
            {
                episodes.Add(episode);
            }

            return episodes;
        }

        private int GetIntFromElement(XElement xElement)
        {
            string value = xElement.Value;
            if (string.IsNullOrEmpty(value))
                return -1;
            else
                return Convert.ToInt32(xElement.Value);
        }

        private FileInfo GetFileInfoFromElement(XElement xElement)
        {
            FileInfo retVal = null;
            try
            {
                string value = xElement.Value;
                if (!string.IsNullOrEmpty(value))
                    retVal = new FileInfo(value);
            }
            catch
            {
                Logger.LogMessage(string.Format("Error parsing FileInfo from element: {0}", xElement));
            }
            return null;
        }

        private FileInfo GetCacheFileInfoFromXElement(DirectoryInfo virtualCacheDir, XElement xElement)
        {
            FileInfo retVal = null;

            try
            {
                string value = xElement.Value;
                string path = Path.Combine(virtualCacheDir.FullName, value);
                retVal = new FileInfo(path);
            }
            catch (Exception e)
            {
                Logger.LogMessage("Error parsing FileInfo from XElement: {0}", e.Message);
            }

            return retVal;
        }

        #endregion

        public void PrintMessage(string message)
        {
            if (m_ProgressReporter != null)
            {
                PasswordForm.Instance.Invoke(m_ProgressReporter, new object[] { message });
            }
        }
    }
}
